/*!
\copyright  Copyright (c) 2022-2025 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
    \ingroup    le_unicast_manager
\brief   Implementations for the LE Unicast Manager message task.
*/

#if defined(INCLUDE_LE_AUDIO_UNICAST)

#include "le_unicast_manager_instance.h"
#include "le_unicast_manager_task.h"
#include "call_control_client.h"
#include "media_control_client.h"
#include "pacs_utilities.h"
#include "gatt_connect.h"
#include "gatt_uuids.h"
#include <gatt.h>
#include <timestamp_event.h>
#include <panic.h>
#include "gatt_tmas_server.h"
#include "tmap_profile.h"
#include "gmap_profile.h"
#include "micp_server.h"
#include "le_unicast_music_source.h"
#include "telephony_messages.h"
#include "mirror_profile.h"
#include "kymera.h"
#include "kymera_le_mic_chain.h"
#include "device_properties.h"
#include "pacs_audio_contexts.h"
#include "gatt_service_discovery.h"

#include "le_unicast_manager_utils.h"
#include "le_unicast_music_source.h"
#include "le_unicast_voice_source.h"
#include "device_db_serialiser.h"
#include "le_unicast_send_event.h"
#include "peer_signalling.h"
#include "audio_ctrl_lea_voice.h"

/*! Offset of client configuration within the structure. */
#define leUnicastManager_GetCCCDOffset()  (offsetof(BapAscsConfig, aseControlPointCharClientCfg))
#define leUnicastManager_GetClientCfgOffset()  (offsetof(BapAscsConfig, aseCharClientCfg))

/*! Minimum GATT MTU needed to support LE unicast. */
#define leUnicastManagerConfig_GattMtuMinimum() 100

/*! Maximum number of active ASEs supported per Gatt connection. */
#define NUMBER_OF_ASES_REQUIRED    6

static void leUnicastManager_OnGattConnect(gatt_cid_t cid);
static void leUnicastManager_OnGattDisconnect(gatt_cid_t cid);
static void leUnicastManager_OnEncryptionChanged(gatt_cid_t cid, bool encrypted);

static const gatt_connect_observer_callback_t leUnicastManager_connect_callbacks =
{
    .OnConnection = leUnicastManager_OnGattConnect,
    .OnDisconnection = leUnicastManager_OnGattDisconnect,
    .OnEncryptionChanged = leUnicastManager_OnEncryptionChanged
};

static bool leUnicastManager_SampleRateLookupOnPacs(bool is_source, uint16 audiocontext, uint16 sample_rate, uint16 codec_type)
{
    bool match_found = FALSE;
    uint16 no_of_pacs;
    uint8 pacs_index;

    switch(codec_type)
    {
        case KYMERA_LE_AUDIO_CODEC_LC3:
        {
            const GattPacsServerRecordType* pacs_record = LeBapPacsUtilities_GetPacs(is_source, &no_of_pacs);

            for (pacs_index = 0; pacs_index < no_of_pacs; pacs_index++)
            {
                AudioContextType pacs_audiocontext = LeBapPacsUtilities_GetPreferredAudioContext(pacs_record[pacs_index].metadata, pacs_record[pacs_index].metadataLength);

                if ((audiocontext & pacs_audiocontext) && (sample_rate & pacs_record[pacs_index].supportedSamplingFrequencies))
                {
                    match_found = TRUE;
                    break;
                }
            }
        }
        break;
#if defined (INCLUDE_LE_APTX_ADAPTIVE) || defined (INCLUDE_LE_AUDIO_GAMING_MODE_APTX_LITE)
        case KYMERA_LE_AUDIO_CODEC_APTX_ADAPTIVE:
        case KYMERA_LE_AUDIO_CODEC_APTX_ADAPTIVE_R4:
        case KYMERA_LE_AUDIO_CODEC_APTX_LITE:
        {
            const GattPacsServerVSPacRecord* pacs_record = LeBapPacsUtilities_GetPacsVS(is_source, &no_of_pacs, codec_type);
            PacsSamplingFrequencyType pacs_sample_rate;

            for (pacs_index = 0; pacs_index < no_of_pacs; pacs_index++)
            {
                AudioContextType pacs_audiocontext = LeBapPacsUtilities_GetPreferredAudioContext(pacs_record[pacs_index].metadata, pacs_record[pacs_index].metadataLength);
                pacs_sample_rate = LeBapPacsUtilities_GetSampleRateFromVSPac(&pacs_record[pacs_index]);

                if ((audiocontext & pacs_audiocontext) && (sample_rate & pacs_sample_rate))
                {
                    match_found = TRUE;
                    break;
                }
            }
        }
        break;
#endif
        default:
            break;
    }

    return match_found;
}

static bool leUnicastManager_ValidateSamplingRateForASE(gatt_cid_t cid, uint16 ase_id, uint16 audiocontext)
{
    bool sampling_rate_matched = FALSE;
    uint8 direction = LeBapUnicastServer_GetAseDirection(cid, ase_id);
    BapServerAseCodecInfo *codec_info = LeBapUnicastServer_GetCodecParameters(cid, ase_id);
    PacsSamplingFrequencyType sample_rate = codec_info == NULL ? 0 :
                    LeBapPacsUtilities_GetPacsSamplingFreqBitMaskFromFreq(LeUnicastManager_GetSampleRate(codec_info));
    appKymeraLeAudioCodec codec_type = codec_info == NULL ? KYMERA_LE_AUDIO_CODEC_LC3 :
                    leUnicastManager_GetCodecType(codec_info);

    switch (direction)
    {
        case GATT_ASCS_ASE_DIRECTION_SERVER_IS_AUDIO_SINK:
            sampling_rate_matched = leUnicastManager_SampleRateLookupOnPacs(FALSE, audiocontext, sample_rate, codec_type);
        break;

        case GATT_ASCS_ASE_DIRECTION_SERVER_IS_AUDIO_SOURCE:
            sampling_rate_matched =  leUnicastManager_SampleRateLookupOnPacs(TRUE, audiocontext, sample_rate, codec_type);
        break;

        default:
        break;
    }

    return sampling_rate_matched;
}

static bool leUnicastManager_IsConversationalContextReqd(le_um_instance_t *inst, AudioContextType current_audio_context)
{
    voice_source_t voice_source = voice_source_none;

    if (inst != NULL)
    {
        voice_source = LeUnicastManager_GetVoiceSourceForInstance(inst);
    }

    /* Check if CCP call state is not Idle and ensure that CCP notification arrived before ASE enable, by checking the UM context is unknown.
     * Or if ASE were enabled first (sequential ase enable) for which the determined
     * audio context was conversational, which means that the audio is currently routed through LE voice, in that case return TRUE.
     */
    if (((CallClientControl_GetContext(voice_source) > context_voice_connected &&
          CallClientControl_GetContext(voice_source) < max_voice_contexts) &&
          current_audio_context == AUDIO_CONTEXT_TYPE_UNKNOWN) ||
          current_audio_context == AUDIO_CONTEXT_TYPE_COVERSATIONAL)
    {
        return TRUE;
    }

    return FALSE;
}

static AudioContextType leUnicastManager_DetermineAudioContext(gatt_cid_t cid, uint8 ase_count, BapServerEnableIndInfo *p_enable_info)
{
    AudioContextType audio_context = AUDIO_CONTEXT_TYPE_UNKNOWN;
    AudioContextType determined_audio_context;
    AudioContextType current_audio_context;
    uint8 scan;
    le_um_instance_t *inst = LeUnicastManager_InstanceGetByCid(cid);

    current_audio_context = inst != NULL ? inst->audio_context : AUDIO_CONTEXT_TYPE_UNKNOWN;
    for (scan = 0; scan < ase_count; scan++)
    {
        audio_context |= BapServerLtvUtilitiesGetStreamingAudioContext(p_enable_info[scan].metadata, p_enable_info[scan].metadataLength);
    }

    determined_audio_context = audio_context;

    /* The audio context is determined when the audio context for the ASE is unknown or has unspecified bit set in it or audio context contains more than 1 context in it */
    if (LeUnicastManager_IsContextOfTypeUnknown(audio_context) ||
        LeUnicastManager_AudioContextHasUnspecifiedContext(audio_context) ||
        LeUnicastManager_AudioContextHasMoreThanOneContext(audio_context))
    {
        uint8 src_ase_count = 0;
        uint8 snk_ase_count = 0;
        uint16 src_sample_rate = 0;
        uint16 snk_sample_rate = 0;
        uint16 sample_rate;

        for (scan = 0; scan < ase_count; scan++)
        {
            const BapServerAseCodecInfo *codec_info = LeBapUnicastServer_GetCodecParameters(cid, p_enable_info[scan].aseId);

            PanicNull((void *) codec_info);
            sample_rate = LeUnicastManager_GetSampleRate(codec_info);

            if ((LeBapUnicastServer_GetAseDirection(cid, p_enable_info[scan].aseId)) == ASE_DIRECTION_AUDIO_SINK)
            {
                snk_ase_count++;
                snk_sample_rate = sample_rate;
            }
            else
            {
                src_ase_count++;
                src_sample_rate = sample_rate;
            }
        }

        if (src_ase_count == 0)
        {
            /* For Sink ASE only, check if CCP context is not idle and the audio is not routed through LE Voice previously else determine context as MEDIA */
            determined_audio_context = leUnicastManager_IsConversationalContextReqd(inst, current_audio_context) ? AUDIO_CONTEXT_TYPE_COVERSATIONAL : AUDIO_CONTEXT_TYPE_MEDIA;
        }
        else if (snk_ase_count == 0)
        {
            /* For Source ASE only, check if CCP context is not idle and the audio is not routed through LE Voice previously else determine context as LIVE */
            determined_audio_context = leUnicastManager_IsConversationalContextReqd(inst, current_audio_context) ? AUDIO_CONTEXT_TYPE_COVERSATIONAL : AUDIO_CONTEXT_TYPE_LIVE;
        }
        else
        {
            determined_audio_context = src_sample_rate == snk_sample_rate ? AUDIO_CONTEXT_TYPE_COVERSATIONAL :
                                            src_sample_rate == SAMPLE_RATE_48000  ? AUDIO_CONTEXT_TYPE_LIVE : AUDIO_CONTEXT_TYPE_GAME;
        }
    }
    else
    {
        /* No need for determining the audio context */
        determined_audio_context = audio_context;
    }

    if (audio_context == (AUDIO_CONTEXT_TYPE_COVERSATIONAL | AUDIO_CONTEXT_TYPE_MEDIA))
    {
        const BapServerAseCodecInfo *codec_info = LeBapUnicastServer_GetCodecParameters(cid, p_enable_info[0].aseId);

        if (codec_info)
        {
            uint16 sample_rate = LeUnicastManager_GetSampleRate(codec_info);

            audio_context = sample_rate == SAMPLE_RATE_48000 ? AUDIO_CONTEXT_TYPE_MEDIA : AUDIO_CONTEXT_TYPE_COVERSATIONAL;
            DEBUG_LOG("leUnicastManager_DetermineAudioContext: sample_rate=%u audio_context=0x%04X", sample_rate, audio_context);
        }
    }

    UNICAST_MANAGER_LOG("leUnicastManager_DetermineAudioContext LeUnicastManager_AudioContext: 0x%x, rx from Remote audio_context: 0x%x, determined_audio_context:0x%x", current_audio_context, audio_context, determined_audio_context);

    return determined_audio_context;
}

/*! \brief Check and move newly Enabled Sink ASEes to streaming state */
static bool leUnicastManager_CheckAndMoveAsesToStreamingState(le_um_instance_t *inst)
{
    bool found = FALSE;
    le_um_ase_t *ase;

    ARRAY_FOREACH(ase, inst->ase)
    {
        if (ase->state == le_um_ase_state_enabling &&
            ase->cis_data != NULL &&
            LeUnicastManager_IsCisEstablished(ase->cis_data->state))
        {
            UNICAST_MANAGER_LOG("leUnicastManager_CheckAndMoveAsesToStreamingState: ASE ID %d", ase->ase_id);

            if (ase->direction == GATT_ASCS_ASE_DIRECTION_SERVER_IS_AUDIO_SINK)
            {
                ase->state = le_um_ase_state_streaming;
                leUnicastManager_ExecuteReadyToReceiveIfReady(inst, ase);
            }

            found = TRUE;
        }
    }

    return found;
}

#ifdef USE_SYNERGY

/*! \brief Handle Config Change ind for ASCS */
static void leUnicastManager_HandleAscsConfigChangeInd(BapServerConfigChangeInd *ind)
{
    BapAscsConfig stored_config = {0};
    BapAscsConfig *service_config = NULL;
    uint8 size = 0;
    uint8 *data;
    bool needs_update = FALSE;

    service_config = (BapAscsConfig *) LeBapUnicastServer_GetServiceConfig(ind->connectionId, ind->configType);
    data = (uint8 *) leUnicastManager_RetrieveClientConfig(ind->connectionId);

    if (data != NULL)
    {
        stored_config = *(BapAscsConfig *) data;
        stored_config.aseCharClientCfg = (ClientConfig *) &data[offsetof(BapAscsConfig, aseCharClientCfg)];
    }
    else
    {
        leUnicastManager_PublishConnectionStatus(ind->connectionId, LE_AUDIO_ASCS_UUID_SERVICE, 0);
    }

    if (service_config != NULL && service_config->aseCharClientCfg != NULL)
    {
        if (service_config->numAses == stored_config.numAses &&
            service_config->aseControlPointCharClientCfg == stored_config.aseControlPointCharClientCfg)
        {
            for (int scan = 0; scan < service_config->numAses; scan++)
            {
                if (service_config->aseCharClientCfg[scan] != stored_config.aseCharClientCfg[scan])
                {
                    needs_update = TRUE;
                    break;
                }
            }
        }
        else
        {
            needs_update = TRUE;
        }

        if (needs_update)
        {
            size = offsetof(BapAscsConfig, aseCharClientCfg) + service_config->numAses * sizeof(ClientConfig);
            data = (uint8 *) PanicUnlessMalloc(size);
            PanicNull(data);

            memcpy(data, &service_config->numAses, sizeof(service_config->numAses));

            memcpy(data + offsetof(BapAscsConfig, aseControlPointCharClientCfg),
                   &service_config->aseControlPointCharClientCfg,
                   sizeof(service_config->aseControlPointCharClientCfg));

            memcpy(data + offsetof(BapAscsConfig, aseCharClientCfg),
                   service_config->aseCharClientCfg,
                   service_config->numAses * sizeof(ClientConfig));

            leUnicastManager_StoreClientConfig(ind->connectionId, data, size);

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler storing configType %d, size %d", ind->configType, size);
            pfree(data);
        }
        pfree (service_config->aseCharClientCfg);
    }
    pfree (service_config);
}

/*! \brief Handle Config Change ind for Pacs */
static void leUnicastManager_HandlePacsConfigChangeInd(BapServerConfigChangeInd *ind)
{
    BapPacsConfig *stored_pacs_config = NULL;
    BapPacsConfig *service_pacs_config = NULL;

    service_pacs_config = (BapPacsConfig *)LeBapUnicastServer_GetServiceConfig(ind->connectionId, ind->configType);
    stored_pacs_config = (BapPacsConfig *)LeBapPacsUtilities_RetrieveClientConfig(ind->connectionId);

    if (stored_pacs_config == NULL)
    {
        leUnicastManager_PublishConnectionStatus(ind->connectionId, LE_AUDIO_PACS_UUID_SERVICE, 0);
    }

    if (service_pacs_config != NULL)
    {
        if (stored_pacs_config == NULL ||
            service_pacs_config->sinkPacClientCfg1 != stored_pacs_config->sinkPacClientCfg1 ||
            service_pacs_config->sinkPacClientCfg2 != stored_pacs_config->sinkPacClientCfg2 ||
            service_pacs_config->sinkPacClientCfg3 != stored_pacs_config->sinkPacClientCfg3 ||
            service_pacs_config->sourcePacClientCfg1 != stored_pacs_config->sourcePacClientCfg1 ||
            service_pacs_config->sourcePacClientCfg2 != stored_pacs_config->sourcePacClientCfg2 ||
            service_pacs_config->sourcePacClientCfg3 != stored_pacs_config->sourcePacClientCfg3 ||
            service_pacs_config->sinkAudioLocationsClientCfg != stored_pacs_config->sinkAudioLocationsClientCfg ||
            service_pacs_config->sourceAudioLocationsClientCfg != stored_pacs_config->sourceAudioLocationsClientCfg ||
            service_pacs_config->availableAudioContextsClientCfg != stored_pacs_config->availableAudioContextsClientCfg ||
            service_pacs_config->supportedAudioContextsClientCfg != stored_pacs_config->supportedAudioContextsClientCfg ||
            service_pacs_config->vsAptXSinkPacClientCfg != stored_pacs_config->vsAptXSinkPacClientCfg ||
            service_pacs_config->vsAptXSourcePacClientCfg != stored_pacs_config->vsAptXSourcePacClientCfg)
        {
            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler storing configType %d", ind->configType);
            LeBapPacsUtilities_StoreClientConfig(ind->connectionId, (void *)service_pacs_config, sizeof(BapPacsConfig));
        }
        pfree (service_pacs_config);
    }
}
#endif

/*! \brief Return TRUE if there is more than one CIS associated with any side */
static bool leUnicastManager_IsMultipleCisUsedForAnySide(le_um_instance_t *inst)
{
#ifdef INCLUDE_MIRRORING
    le_um_ase_t *l_sink_ase, *l_source_ase, *r_sink_ase, *r_source_ase;
    bool status = FALSE;

    leUnicastManager_GetAseFromSide(inst, multidevice_side_left, &l_sink_ase, &l_source_ase);
    leUnicastManager_GetAseFromSide(inst, multidevice_side_right, &r_sink_ase, &r_source_ase);

    if ((leUnicastManager_IsAseActive(l_sink_ase) && leUnicastManager_IsAseActive(l_source_ase) &&
         l_sink_ase->qos_info->cisId != l_source_ase->qos_info->cisId) ||
        (leUnicastManager_IsAseActive(r_sink_ase) && leUnicastManager_IsAseActive(r_source_ase) &&
         r_sink_ase->qos_info->cisId != r_source_ase->qos_info->cisId))
    {
        status = TRUE;
    }

    UNICAST_MANAGER_LOG("leUnicastManager_IsMultipleCisUsedForAnySide %d", status);

    return status;
#else
    UNUSED(inst);
    /* If mirroring is not enabled, then no harm in sending LE_AUDIO_UNICAST_ENABLED. */
    return FALSE;
#endif /* INCLUDE_MIRRORING */
}

#ifdef ENABLE_LE_MULTIPOINT_BACKGROUND_CIS_SUPPORT

static void leUnicastManager_SendEnableUnicastSessionLater(le_um_instance_t *inst)
{
    MESSAGE_MAKE(message, LE_UM_INTERNAL_ENABLE_UNICAST_SESSION_LATER_T);

    /* This is a new unicast session, but for the same device a previous 
     * session is still pending(possibly to be cleared) in the background
     * Pend this source addition for later.
     */
    message->instance = inst;
    MessageCancelFirst(LeUnicastManager_GetTask(), LE_UM_INTERNAL_ENABLE_UNICAST_SESSION_LATER);
    MessageSendLater(LeUnicastManager_GetTask(), LE_UM_INTERNAL_ENABLE_UNICAST_SESSION_LATER, message,
                     LeUnicastManager_SessionPendingTimeout());

    UNICAST_MANAGER_LOG("leUnicastManager_SendEnableUnicastSessionLater cid=0x%x", inst->cid);
}

#endif

static void leUnicastManager_HandleBapAseEnabledInd(le_um_instance_t *inst, BapServerAseEnabledInd *ind)
{
    uint8 i;
    GattAscsAseResultValue result = inst != NULL ? GATT_ASCS_ASE_RESULT_SUCCESS : GATT_ASCS_ASE_RESULT_UNSPECIFIED_ERROR;
    AudioContextType audio_context;
    BapServerAseResult *aseResult =  PanicUnlessMalloc(ind->numAses * sizeof(BapServerAseResult));
    bool is_sink;
    AseMetadataType metadata;
    bool is_multiperipheral_cis_enabled = FALSE;

    audio_context = leUnicastManager_DetermineAudioContext(ind->connectionId, ind->numAses, ind->bapServerEnableIndInfo);

    for (i = 0; i < ind->numAses; i++)
    {
        aseResult[i].aseId = ind->bapServerEnableIndInfo[i].aseId;
        aseResult[i].additionalInfo = 0;

        if (result == GATT_ASCS_ASE_RESULT_SUCCESS)
        {
            result = BapServerValidateMetadataLtvs(ind->bapServerEnableIndInfo[i].metadata,
                                                   ind->bapServerEnableIndInfo[i].metadataLength,
                                                   &aseResult[i].additionalInfo);
        }

        if (result == GATT_ASCS_ASE_RESULT_SUCCESS)
        {
            is_sink = LeBapUnicastServer_GetAseDirection(ind->connectionId, ind->bapServerEnableIndInfo[i].aseId) == ASE_DIRECTION_AUDIO_SINK;
            if (LeBapPacsUtilities_ValidateStreamingContext(ind->connectionId,
                                                            is_sink,
                                                            inst->audio_context,
                                                            ind->bapServerEnableIndInfo[i].metadata,
                                                            &ind->bapServerEnableIndInfo[i].metadataLength) &&
                leUnicastManager_ValidateSamplingRateForASE(ind->connectionId,
                                                            ind->bapServerEnableIndInfo[i].aseId,
                                                            audio_context))
            {

                metadata.aseId = ind->bapServerEnableIndInfo[i].aseId;
                metadata.metadataLength = ind->bapServerEnableIndInfo[i].metadata == NULL ? 0 : ind->bapServerEnableIndInfo[i].metadataLength;
                metadata.metadata = ind->bapServerEnableIndInfo[i].metadata;
                is_multiperipheral_cis_enabled = LtvUtilities_FindIfMultiperipheralCisIsSupported(metadata.metadata, metadata.metadataLength);

                if (!leUnicastManager_AseEnabled(ind->connectionId, &metadata, audio_context))
                {
                    result = GATT_ASCS_ASE_RESULT_UNSPECIFIED_ERROR;
                    aseResult[i].additionalInfo = 0;
                }
            }
            else
            {
                result = GATT_ASCS_ASE_RESULT_REJECTED_METADATA;
                aseResult[i].additionalInfo = BAP_METADATA_LTV_TYPE_STREAMING_AUDIO_CONTEXTS;
            }
        }

        aseResult[i].value = result;
        DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_ENABLED_IND cid=0x%x ase=0x%x cis: 0x%x, audio_context: 0x%x, result=0x%x, additional_info: 0x%x",
                       ind->connectionId, ind->bapServerEnableIndInfo[i].aseId, ind->bapServerEnableIndInfo[i].cisId, audio_context, aseResult[i].value, aseResult[i].additionalInfo);
    }

#ifdef INCLUDE_MIRRORING
    if (result == GATT_ASCS_ASE_RESULT_SUCCESS && !leUnicastManager_UpdateMirrorType(inst, is_multiperipheral_cis_enabled))
    {
        result = GATT_ASCS_ASE_RESULT_UNSPECIFIED_ERROR;
    }
#else
    UNUSED(is_multiperipheral_cis_enabled);
#endif

    if (result != GATT_ASCS_ASE_RESULT_SUCCESS)
    {
        for (i = 0; i < ind->numAses; i++)
        {
            if (aseResult[i].value == GATT_ASCS_ASE_RESULT_SUCCESS)
            {
                aseResult[i].value = result;
                leUnicastManager_AseDisabled(ind->connectionId, ind->bapServerEnableIndInfo[i].aseId);
            }
        }
    }
    else
    {
        /* Claim the audio context conditionally */
        leUnicastManager_UpdateAvailableAudioContextsIfReqd(inst, audio_context, TRUE)
    }

    BapServerUnicastAseEnableRsp(bapUnicastServiceHandle, ind->connectionId, ind->numAses, aseResult);

    if (result == GATT_ASCS_ASE_RESULT_SUCCESS && inst != NULL)
    {
        uint32 start_time;

        TimestampEvent(TIMESTAMP_EVENT_LE_UNICAST_ASCS_ENABLE);
        start_time = TimestampEvent_GetTime(TIMESTAMP_EVENT_LE_UNICAST_ASCS_ENABLE);
        DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler: BAP_SERVER_ASE_ENABLED_IND start_time %d", start_time);

        /* Check and move newly enabled ASEes to Streaming state */
        (void) leUnicastManager_CheckAndMoveAsesToStreamingState(inst);

        /* Check if we were already rendering audio related to this instance due to partial ASE enabled previously */
        if (inst->source_state == source_state_connected)
        {
            /* Do not send LE_AUDIO_UNICAST_ENABLED if there is more than one CIS associated with same side. 
               (As of now we do not support mirroring for it) */
            if (!leUnicastManager_IsMultipleCisUsedForAnySide(inst))
            {
                leUnicastManager_SendUnicastEnabledInd(inst->cid, inst->audio_context, TRUE);
            }

            /* Reconfig audio graph if required */
            leUnicastManager_CheckAndReconfigAudio(inst);
        }
        else if (inst->source_state == source_state_connecting)
        {
            /* Still focus is available but pending CIS/Datapath hence just publish updated enable information */
            leUnicastManager_SendUnicastEnabledInd(inst->cid, inst->audio_context, TRUE);

            UNICAST_MANAGER_LOG("leUnicastManager_BapMessageHandler: source_state_connecting awaiting CIS/Data path setup");
        }
        else
        {
            {
                inst->source_state = source_state_invalid;
#ifdef ENABLE_LE_MULTIPOINT_BACKGROUND_CIS_SUPPORT
                if (MirrorProfile_IsUnicastSessionExistInBackground(inst->cid))
                {
                    leUnicastManager_SendEnableUnicastSessionLater(inst);
                }
                else
#endif /* ENABLE_LE_MULTIPOINT_BACKGROUND_CIS_SUPPORT */
                {
                    leUnicastManager_UpdateAudioConnected(inst);
                }
            }

            /* Update the MRU device */
            leUnicastManager_SetMruFromCid(inst->cid);
        }
    }

    pfree(aseResult);
}

static bool leUnicastManager_CanProcessAseEnableInd(le_um_instance_t *this_inst)
{
    le_um_instance_t *other_inst = LeUnicastManager_GetOtherInstance(this_inst);

   /* When two handsets simultaneously starts the unicast session and before the application could process the
    * LE_AUDIO_UNICAST_ENABLED message from first handset, unicast manager gets the ASE enable ind from the second handset,
    * in such case, there could be discrepancy with the audio context management, if its been set from the application upon
    * LE_AUDIO_UNICAST_ENABLED.
    *
    * A typical path for LE_AUDIO_UNICAST_ENABLED message to be sent is - upon ASE enabled ind, LE_AUDIO_UNICAST_MEDIA_CONNECTED/
    * LE_AUDIO_UNICAST_VOICE_CONNECTED is sent to media player/telephony service, which would add the source in audio router,
    * and callbacks unicast manager with source state connecting, which would set the instance state as "source_state_connecting",
    * upon which the unicast session is started and LE_AUDIO_UNICAST_ENABLE is then sent.
    *
    * To avoid the above race condition, this function shall not allow the ASE enabled from second handset to process if
    * 1. Another handset is present in the unicast manager and a stream has just been enabled for it. AND
    * 2. Either: a. The LE_AUDIO_UNICAST_MEDIA_CONNECTED/LE_AUDIO_UNICAST_VOICE_CONNECTED message has been processed,
    *               but the application has not yet processed the LE_AUDIO_UNICAST_ENABLED message to set the available audio
    *               context for the other handset. OR 
    *            b. The LE_AUDIO_UNICAST_MEDIA_CONNECTED/LE_AUDIO_UNICAST_VOICE_CONNECTED message has not been processed yet,
    *               so the LE_AUDIO_UNICAST_ENABLED message has not been sent at all.
    */
    if (other_inst != NULL &&
        LeUnicastManager_IsAnyAseEnabled(other_inst) &&
        ((other_inst->source_state == source_state_connecting &&
          LeAudioMessages_IsMessagePendingForAnyClient(LE_AUDIO_UNICAST_ENABLED)) ||
         (LeAudioMessages_IsMessagePendingForAnyClient(LE_AUDIO_UNICAST_VOICE_CONNECTED) ||
          LeAudioMessages_IsMessagePendingForAnyClient(LE_AUDIO_UNICAST_MEDIA_CONNECTED))))
    {
        return FALSE;
    }

    return TRUE;
}

static void leUnicastManager_BapMessageHandler(Message message)
{
    BapServerPrim id;

    PanicNull((void*) message);

#ifdef USE_SYNERGY
    id = *(BapServerPrim *) message;
#else
    id = 0xFFFF;
#endif

    switch(id)
    {
        case BAP_SERVER_ASE_CODEC_CONFIGURED_IND:
        {
            BapServerAseCodecConfiguredInd *ind = (BapServerAseCodecConfiguredInd *) message;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_CODEC_CONFIGURED_IND cid=0x%x ase=0x%x direction: %d",
                           ind->connectionId, ind->aseCodecConfig.aseId, ind->aseCodecConfig.direction);

        }
        break;

        case BAP_SERVER_ASE_QOS_CONFIGURED_IND:
        {
            BapServerAseQosConfiguredInd *ind = (BapServerAseQosConfiguredInd *) message;
            uint32 start_time = 0;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_QOS_CONFIGURED_IND cid=0x%x ase=0x%x cis: 0x%x",
                           ind->connectionId, ind->aseQosConfig.aseId, ind->aseQosConfig.cisId);
            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_QOS_CONFIGURED_IND sampleRate=%d sduSize: %d, frameDuration: %d, presentationDelay: %d",
                           ind->aseQosConfig.sampleRate, ind->aseQosConfig.sduSize, ind->aseQosConfig.frameDuration, ind->aseQosConfig.presentationDelay);

            TimestampEvent(TIMESTAMP_EVENT_LE_UNICAST_ASCS_CONFIGURE_QOS);
            start_time = TimestampEvent_GetTime(TIMESTAMP_EVENT_LE_UNICAST_ASCS_CONFIGURE_QOS);
            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_QOS_CONFIGURED_IND start_time %d",
                                 start_time);
        }
        break;

        case BAP_SERVER_ASE_ENABLED_IND:
        {
            BapServerAseEnabledInd *ind = (BapServerAseEnabledInd *) message;
            le_um_instance_t *inst = LeUnicastManager_InstanceGetByCid(ind->connectionId);

            if (inst != NULL && ind->connectionId && ind->numAses)
            {
                if (leUnicastManager_CanProcessAseEnableInd(inst))
                {
                    leUnicastManager_HandleBapAseEnabledInd(inst, ind);
                }
                else
                {
                    size_t msg_size = sizeof(BapServerAseEnabledInd) +
                                      ((ind->numAses - 1) * sizeof(BapServerEnableIndInfo));
                    BapServerAseEnabledInd *msg_resend = (BapServerAseEnabledInd *) PanicUnlessMalloc(msg_size);

                    DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : Requeue BAP_SERVER_ASE_ENABLED_IND cid=0x%x", ind->connectionId);

                    memcpy(msg_resend, ind, msg_size);
                    MessageSend(LeUnicastManager_GetTask(), BAP_SRVR_PRIM, msg_resend);
                }
            }
        }
        break;

        case BAP_SERVER_CIS_ESTABLISHED_IND:
        {
            BapServerCisEstablishedInd *ind = (BapServerCisEstablishedInd *) message;
            uint8 dir;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler BAP_SERVER_CIS_ESTABLISHED_IND cid: 0x%x cis: 0x%x, handle: 0x%x",
                           ind->connectionId, ind->cisId, ind->cisHandle);

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler CIS Params cigSyncDelay: 0x%x, cisSyncDelay: 0x%x, isoInt: 0x%x, nse: 0x%x",
                           ind->cisParams.cigSyncDelay, ind->cisParams.cisSyncDelay, ind->cisParams.isoInterval, ind->cisParams.nse);
            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler CIS Params MtoS transLat: 0x%x, maxPDU: 0x%x, phy: 0x%x, bn: 0x%x, ft: 0x%x",
                           ind->cisParams.transportLatencyMtoS, ind->cisParams.maxPduMtoS, ind->cisParams.phyMtoS, ind->cisParams.bnMtoS,
                           ind->cisParams.ftMtoS);
            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler CIS Params StoM transLat: 0x%x, maxPDU: 0x%x, phy: 0x%x, bn: 0x%x, ft: 0x%x",
                           ind->cisParams.transportLatencyStoM, ind->cisParams.maxPduStoM, ind->cisParams.phyStoM, ind->cisParams.bnStoM,
                           ind->cisParams.ftStoM);

            dir = ind->cisParams.bnMtoS != 0 ? LE_UM_CIS_DIRECTION_DL : 0;
            dir |= ind->cisParams.bnStoM != 0 ? LE_UM_CIS_DIRECTION_UL : 0;
            leUnicastManager_SendCisEstablishedEvent(ind);
            /* Process for id CIS ID is valid */
            if (ind->cisId != LE_INVALID_CIS_ID)
            {
                leUnicastManager_CisEstablished(ind->connectionId, ind->cisId, ind->cisHandle, dir);
            }
        }
        break;

        case BAP_SERVER_SETUP_DATA_PATH_CFM:
        {
            BapServerSetupDataPathCfm *cfm = (BapServerSetupDataPathCfm *) message;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler BAP_SERVER_SETUP_DATA_PATH_CFM handle: 0x%x, status: 0x%x",
                           cfm->isoHandle, cfm->status);
            leUnicastManager_SendSetupDataPathCommandCompletEvent(cfm);

            /* In few multipoint scenario, when the source is transitioned from foreground to background and one bud is
               in case, the application may not remove all the datapath, so upon creating datapath again (while 
               transitioning to foreground again), the controller may send status as command disallowed, which also
               needs to be handled as datapath was never removed. */
            if (cfm->status == BAP_SERVER_STATUS_SUCCESS || cfm->status == HCI_ERROR_COMMAND_DISALLOWED)
            {
                leUnicastManager_DataPathCreated(cfm->isoHandle);
            }
        }
        break;

        case BAP_SERVER_CIS_DISCONNECTED_IND:
        {
            BapServerCisDisconnectedInd *ind = (BapServerCisDisconnectedInd *) message;
            le_um_instance_t *inst = LeUnicastManager_InstanceGetByCisHandle(ind->cisHandle);
            acl_link_type_t link_type = cis_link;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler BAP_SERVER_CIS_DISCONNECTED_IND CIS handle: 0x%x, reason: 0x%x",
                           ind->cisHandle, ind->reason);

            if (inst != NULL)
            {
                le_um_ase_t *sink_ase, *source_ase;

                if (ind->reason == hci_error_conn_timeout || ind->reason == hci_error_lmp_response_timeout)
                {
#ifdef INCLUDE_MIRRORING
#if defined (ENABLE_LE_MULTIPOINT) && !defined (ENABLE_LE_MULTIPOINT_BACKGROUND_CIS_SUPPORT)
                    inst->source_link_loss = TRUE;
#endif
#endif
                    le_um_internal_msg_t msg = LeUnicastManager_GetCisLinklossMessageForInst(inst);
                    MessageCancelFirst(LeUnicastManager_GetTask(), msg);
                    MessageSendLater(LeUnicastManager_GetTask(), msg, NULL,
                                     LeUnicastManager_CisLinklossConfirmationTimeout());
                }

                leUnicastManager_GetAseFromSide(inst, Multidevice_GetSide(), &sink_ase, &source_ase);
                if (!appDeviceIsPeerConnected() ||
                    (sink_ase->cis_data != NULL && sink_ase->cis_data->cis_handle == ind->cisHandle) ||
                    (source_ase->cis_data != NULL && source_ase->cis_data->cis_handle == ind->cisHandle))
                {
                    leUnicastManager_SendDisconnectionCompleteEvent(ind->cisHandle, ind->reason, link_type);
                }
            }

            leUnicastManager_CisDisconnected(ind->cisHandle);
        }
        break;

        case BAP_SERVER_CIS_DISCONNECTED_CFM:
        {
            BapServerCisDisconnectedCfm *cfm = (BapServerCisDisconnectedCfm *) message;
            acl_link_type_t link_type = cis_link;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler BAP_SERVER_CIS_DISCONNECTED_CFM CIS handle: 0x%x, reason: 0x%x",
                           cfm->cisHandle, cfm->status);

            leUnicastManager_SendDisconnectionCompleteEvent(cfm->cisHandle, cfm->status, link_type);
            leUnicastManager_CisDisconnected(cfm->cisHandle);
        }
        break;

        case BAP_SERVER_ASE_RECEIVER_START_READY_IND:
        {
            BapServerAseReceiverStartReadyInd *ind = (BapServerAseReceiverStartReadyInd *)message;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_RECEIVER_START_READY_IND cid=0x%x ase=0x%x",
                           ind->connectionId, ind->aseId);
            leUnicastManager_AseReceiverReady(ind->connectionId, ind->aseId);
        }
        break;

        case BAP_SERVER_ASE_UPDATE_METADATA_IND:
        {
            BapServerAseUpdateMetadataInd *ind = (BapServerAseUpdateMetadataInd *) message;
            AudioContextType audioContext;
            le_um_ase_t *ase_data;
            GattAscsAseResultValue result = GATT_ASCS_ASE_RESULT_SUCCESS;
            uint8 i;
            le_um_instance_t *inst = LeUnicastManager_InstanceGetByCid(ind->connectionId);

            if (ind->connectionId && ind->numAses)
            {
                BapServerAseResult *aseResult =  PanicUnlessMalloc(ind->numAses * sizeof(BapServerAseResult));

                for(i = 0; i < ind->numAses; i++)
                {
                    aseResult[i].aseId = ind->bapServerUpdateMetadataInfo[i].aseId;
                    aseResult[i].additionalInfo = 0;
                    ase_data = leUnicastManager_GetAse(ind->connectionId, aseResult[i].aseId);

                    result = BapServerValidateMetadataLtvs(ind->bapServerUpdateMetadataInfo[i].metadata,
                                                           ind->bapServerUpdateMetadataInfo[i].metadataLength,
                                                           &aseResult[i].additionalInfo);
                    audioContext = BapServerLtvUtilitiesGetStreamingAudioContext(ind->bapServerUpdateMetadataInfo[i].metadata, ind->bapServerUpdateMetadataInfo[i].metadataLength);

                    if (result == GATT_ASCS_ASE_RESULT_SUCCESS)
                    {
                        /* check if the audio context is available */
                        if (!BapServerValidateStreamingContext(bapUnicastServiceHandle, ind->connectionId,
                                                              ind->bapServerUpdateMetadataInfo[i].aseId,
                                                              ind->bapServerUpdateMetadataInfo[i].metadata,
                                                              &ind->bapServerUpdateMetadataInfo[i].metadataLength))
                        {
                            /* Check if this audio context is already claimed by ADK earlier */
                            if ((inst != NULL) && (ase_data != NULL && (ase_data->audio_context & audioContext) == audioContext))
                            {
                                result = GATT_ASCS_ASE_RESULT_SUCCESS;
                            }
                            else
                            {
                                result = GATT_ASCS_ASE_RESULT_REJECTED_METADATA;
                                aseResult[i].additionalInfo = BAP_METADATA_LTV_TYPE_STREAMING_AUDIO_CONTEXTS;
                            }
                        }
                    }

                    /* Accept the valid metadata update */
                    aseResult[i].value = result;

                    DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_UPDATE_METADATA_IND ase=0x%x audio_context = 0x%x, result = 0x%x, additional_info: 0x%x",
                                    ind->bapServerUpdateMetadataInfo[i].aseId, audioContext, aseResult[i].value, aseResult[i].additionalInfo);
                }

                BapServerUnicastAseUpdateMetadataRsp(bapUnicastServiceHandle, ind->connectionId, ind->numAses, aseResult);
                pfree(aseResult);
            }
        }
        break;

        case BAP_SERVER_ASE_DISABLED_IND:
        {
            BapServerAseDisabledInd *ind = (BapServerAseDisabledInd *) message;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_DISABLED_IND cid=0x%x ase=0x%x",
                           ind->connectionId, ind->aseId);
            leUnicastManager_AseDisabled(ind->connectionId, ind->aseId);
        }
        break;

        case BAP_SERVER_ASE_RECEIVER_STOP_READY_IND:
        {
            BapServerAseReceiverStopReadyInd *ind = (BapServerAseReceiverStopReadyInd *) message;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_RECEIVER_STOP_READY_IND cid=0x%x ase=0x%x",
                           ind->connectionId, ind->aseId);
            leUnicastManager_AseReceiverStop(ind->connectionId, ind->aseId);
        }
        break;

        case BAP_SERVER_ASE_RELEASED_IND:
        {
            BapServerAseReleasedInd *ind = (BapServerAseReleasedInd *) message;

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler : BAP_SERVER_ASE_RELEASED_IND cid=0x%x ase=0x%x",
                           ind->connectionId, ind->aseId);
            leUnicastManager_AseReleased(ind->connectionId, ind->aseId);
        }
        break;

        case BAP_SERVER_REMOVE_DATA_PATH_CFM:
        {
            BapServerRemoveDataPathCfm *cfm = (BapServerRemoveDataPathCfm *) message;

            leUnicastManager_SendRemoveDataPathCommandCompletEvent(cfm);
            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler BAP_SERVER_REMOVE_DATA_PATH_CFM handle: 0x%x, status: 0x%x",
                           cfm->isoHandle, cfm->status);

            if (cfm->status == BAP_SERVER_STATUS_SUCCESS)
            {
                leUnicastManager_DataPathRemoved(cfm->isoHandle);
            }
        }
        break;

#ifdef USE_SYNERGY
        case BAP_SERVER_CONFIG_CHANGE_IND:
        {
            le_um_instance_t *inst;
            BapServerConfigChangeInd *ind = (BapServerConfigChangeInd *) message;
            device_t device = GattConnect_GetBtDevice(ind->connectionId);

            /* Mark this device as LEA capable */
            if (device != NULL)
            {
                BtDevice_AddSupportedProfilesToDevice(device, DEVICE_PROFILE_LE_AUDIO);
            }
            inst = LeUnicastManager_InstanceGetByCidOrCreate(ind->connectionId);

            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler BAP_SERVER_CONFIG_CHANGE_IND cid: 0x%x, configType %d, changeCmplt: %d inst: 0x%x",
                            ind->connectionId, ind->configType, ind->configChangeComplete, inst);

            if (ind->configChangeComplete)
            {
                if (ind->configType == BAP_SERVER_CONFIG_ASCS)
                {
                    leUnicastManager_HandleAscsConfigChangeInd(ind);
                }
                else if (ind->configType == BAP_SERVER_CONFIG_PACS)
                {
                    leUnicastManager_HandlePacsConfigChangeInd(ind);
                }
            }
        }
        break;

        case BAP_SERVER_AVAILABLE_AUDIO_CONTEXT_READ_IND:
        {
            BapServerAvailableAudioContextReadInd *ind = (BapServerAvailableAudioContextReadInd *) message;
            audio_contexts_config_t audio_context = {0};

            PacsAudioContexts_GetSelectiveAudioContexts(ind->connectionId, &audio_context);
            BapServerAvailableAudioContextReadResponse(bapUnicastServiceHandle,
                                                       ind->connectionId,
                                                       audio_context.sink,
                                                       audio_context.source);
        }
        break;
#endif

        default:
            DEBUG_LOG_INFO("leUnicastManager_BapMessageHandler: unhandled message 0x%04X", id);
        break;
    }
}

static void leUnicastManager_HandleMuteInd(MICP_SERVER_MUTE_IND_T *ind)
{
    uint8 mute_state = ind->mute_state;
    bool mic_active = FALSE;
    AudioContextType audio_context;
    voice_source_t source;

    le_um_instance_t *inst = LeUnicastManager_GetInstanceByFocus();

    if (inst == NULL)
    {
        return;
    }

    if (ind->cid != INVALID_CID && ind->cid != inst->cid)
    {
        return;
    }

    audio_context = inst->audio_context;
    source = LeUnicastManager_GetVoiceSourceForInstance(inst);

    if (!LeUnicastManager_IsStreamingActive())
    {
        return;
    }

    if (LeUnicastManager_IsContextTypeConversational(audio_context))
    {
        if (VoiceSources_IsAudioRouted(source))
        {
            /* Notify Telephony mute state is active /inactive
            TODO : Expand this message as a generic for Gaming Mode + VBC and SREC use case */
            if (mute_state)
            {
                Telephony_NotifyMicrophoneMuted(source);
            }
            else
            {
                Telephony_NotifyMicrophoneUnmuted(source);
            }

            mic_active = TRUE;
        }
    }
    else if (LeUnicastManager_IsSourceAseActive(inst))
    {
        mic_active = TRUE;
    }

    UNICAST_MANAGER_INFO("leUnicastManager_HandleMuteInd mute-state:=%d, mic-active: %d", ind->mute_state, mic_active);
    if (mic_active)
    {
        AudioCtrlLeaVoice_SetMicMuteState(mute_state);

#ifdef ENABLE_LEA_CIS_DELEGATION
        MirrorProfile_SendMicMute(mute_state);
#endif
    }
}


static void leUnicastManager_CmMessageHandler(Message message)
{
    CsrBtCmPrim *prim = (CsrBtCmPrim *) message;

    switch (*prim)
    {


        default:
            break;
    }

    CmFreeUpstreamMessageContents(message);
}

#ifdef ENABLE_LEA_CIS_DELEGATION
/*! \brief Handle peer sig profile connection indication */
static void leUnicastManager_HandlePeerSigConnectionInd(PEER_SIG_CONNECTION_IND_T *ind)
{
    if (!MirrorProfile_IsRolePrimary() &&
        (ind->status == peerSigStatusDisconnected || ind->status == peerSigStatusLinkLoss))
    {
        /* Peer sig profile connection with primary is lost, so as secondary we no longer able to get the
           updates of LE audio or LE voice sources over device_sync. So clear all the allocated sources here.
           If peer connection later re-establishes with primary, the immediate device sync will ensure
           the sources are synced properly */
        LeUnicastManager_RemoveAllLeAudioVoiceSources();
    }
}
#endif /* ENABLE_LEA_CIS_DELEGATION */

static void leUnicastManager_HandleServiceRange(const GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM_T* message)
{
    device_t device = GattConnect_GetBtDevice(message->cid);

    if (message->result == GATT_SD_RESULT_SUCCESS && message->srvcInfoCount != 0)
    {
        if (device != NULL)
        {
            BtDevice_AddSupportedProfilesToDevice(device, DEVICE_PROFILE_LE_AUDIO);
            (void) LeUnicastManager_InstanceGetByCidOrCreate(message->cid);
        }
        else
        {
            /* Device is NULL, so it means that the LE connection is over random address and not able to resolve the 
               bd-addr. This is a race condition when AG connects over LE (fresh pairing) and LE service discovery 
               is done before pairing completes. So, we end up not adding the device property which could lead to
               further issues.
               So, we better update the property as LE capable, so that when link encrypts we end up adding the LE instance
               for the device
             */
            device = GattConnect_GetBtLeDevice(message->cid);
            PanicNull(device);
            BtDevice_AddSupportedProfilesToDevice(device, DEVICE_PROFILE_LE_AUDIO);
        }

        pfree(message->srvcInfo);
    }

    DEBUG_LOG_INFO("leUnicastManager_HandleServiceRange Number of LEA Services cid: 0x%x, result: %d, Found: %d device : %p",
                   message->cid, message->result, message->srvcInfoCount, device);
}

static void leUnicastManager_HandleGattPrim(Message message)
{
    switch (*(CsrBtCmPrim *)message)
    {
        case GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM:
            leUnicastManager_HandleServiceRange((const GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM_T*)message);
        break;

        default:
        break;
    }
}

static void leUnicastManager_MessageHandler(Task task, MessageId id, Message message)
{
    UNUSED(task);

    switch (id)
    {
        case BAP_SRVR_PRIM:
            leUnicastManager_BapMessageHandler(message);
            break;

        case GATT_SD_PRIM:
            leUnicastManager_HandleGattPrim(message);
            break;

        case MICP_SERVER_MUTE_IND:
            leUnicastManager_HandleMuteInd((MICP_SERVER_MUTE_IND_T *)message);
            break;

#ifdef USE_SYNERGY
        case CM_PRIM:
            leUnicastManager_CmMessageHandler(message);
            break;
#endif

#ifdef ENABLE_LEA_CIS_DELEGATION
        case PEER_SIG_CONNECTION_IND:
            leUnicastManager_HandlePeerSigConnectionInd((PEER_SIG_CONNECTION_IND_T *)message);
            break;
#endif /* ENABLE_LEA_CIS_DELEGATION */

        case LE_UM_INTERNAL_CCP_NOTIFICATION_PENDING_TIMEOUT:
        {
            voice_source_t source = ((LE_UM_INTERNAL_CONVERSATIONAL_SESSION_ENABLED_T *)message)->source;
            le_um_instance_t *inst = LeUnicastManager_InstanceGetByVoiceSource(source);

            if (inst != NULL &&
                LeUnicastManager_IsContextTypeConversational(inst->audio_context) &&
                LeUnicastManager_IsAnyAseEnabled(inst))
            {
                if (CallClientControl_GetContext(source) == context_voice_connected)
                {
                    Telephony_NotifyCallActive(source);
                }
                else
                {
                    inst->ccp_notified = TRUE;
                }
            }
        }
            break;

#ifdef ENABLE_LE_MULTIPOINT_BACKGROUND_CIS_SUPPORT
        case LE_UM_INTERNAL_ENABLE_UNICAST_SESSION_LATER:
        {
            le_um_instance_t *inst = ((LE_UM_INTERNAL_ENABLE_UNICAST_SESSION_LATER_T *) message)->instance;

            DEBUG_LOG_INFO("leUnicastManager_MessageHandler LE_UM_INTERNAL_ENABLE_UNICAST_SESSION_LATER cid: 0x%08x", inst->cid);

            if (inst->cid != INVALID_CID && LeUnicastManager_IsAnyAseEnabled(inst))
            {
                if (MirrorProfile_IsUnicastSessionExistInBackground(inst->cid))
                {
                    leUnicastManager_SendEnableUnicastSessionLater(inst);
                }
                else
                {
                    leUnicastManager_UpdateAudioConnected(inst);
                }
            }
        }
        break;
#endif /* ENABLE_LE_MULTIPOINT_BACKGROUND_CIS_SUPPORT */

        case GATT_SERVICE_DISCOVERY_COMPLETE:
        {
            gatt_cid_t cid = ((const GATT_SERVICE_DISCOVERY_COMPLETE_T*) message)->cid;
            device_t device = GattConnect_GetBtDevice(cid);

            /* Start looking for LEA services in handset for fresh connections only */
            if (!BtDevice_IsProfileSupportedForDevice(device, DEVICE_PROFILE_LE_AUDIO))
            {
            GattServiceDiscoveryFindServiceRange(TrapToOxygenTask((Task)LeUnicastManager_GetTask()),
                                                     cid,
                                                     GATT_SD_GMCS_SRVC|GATT_SD_TBS_SRVC|GATT_SD_TMAS_SRVC|GATT_SD_GMAS_SRVC);
            }
        }
        break;

        default:
            break;
    }
}

static void leUnicastManager_OnGattConnect(gatt_cid_t cid)
{
    UNUSED(cid);

    TimestampEvent(TIMESTAMP_EVENT_LE_UNICAST_ACL_CONNECT);
}

static void leUnicastManager_OnGattDisconnect(gatt_cid_t cid)
{
    BapAscsConfig *config = BapServerUnicastRemoveAscsConfig(bapUnicastServiceHandle, cid);

    UNICAST_MANAGER_LOG("leUnicastManager_OnGattDisconnect cid=0x%x", cid);

    if(config)
    {
#ifdef USE_SYNERGY
        uint8 size = offsetof(BapAscsConfig, aseCharClientCfg) + config->numAses * sizeof(ClientConfig);
        uint8 *data = (uint8 *) PanicUnlessMalloc(size);

        memcpy(data, &config->numAses, sizeof(config->numAses));
        memcpy(data + offsetof(BapAscsConfig, aseControlPointCharClientCfg),
               &config->aseControlPointCharClientCfg,
               sizeof(config->aseControlPointCharClientCfg));
        memcpy(data + offsetof(BapAscsConfig, aseCharClientCfg),
               config->aseCharClientCfg,
               config->numAses * sizeof(ClientConfig));

        leUnicastManager_StoreClientConfig(cid, data, size);
        pfree(data);

        pfree(config->aseCharClientCfg);
#endif
        if(!GattConnect_IsDeviceTypeOfPeer(cid) &&
           GattConnect_GetDisconnectReasonCode(cid) != gatt_connect_disconnect_reason_link_transferred)
        {
            leUnicastManager_PublishConnectionStatus(cid, LE_AUDIO_ASCS_UUID_SERVICE, -1);
            leUnicastManager_PublishConnectionStatus(cid, LE_AUDIO_PACS_UUID_SERVICE, -1);
        }

        pfree(config);
    }

    leUnicastManager_CidDisconnected(cid, GattConnect_GetDisconnectReasonCode(cid));
}

static void leUnicastManager_OnEncryptionChanged(gatt_cid_t cid, bool encrypted)
{
    if (encrypted && !GattConnect_IsDeviceTypeOfPeer(cid))
    {
        uint8 *data = NULL;
        BapAscsConfig config = {0};
        bapStatus status;
        device_t device = GattConnect_GetBtDevice(cid);

#ifdef USE_SYNERGY
        data = (uint8 *)leUnicastManager_RetrieveClientConfig(cid);

        if (data != NULL)
        {
            memcpy(&config.numAses, data, sizeof(config.numAses));
            memcpy(&config.aseControlPointCharClientCfg,
                   data + leUnicastManager_GetCCCDOffset(),
                   sizeof(config.aseControlPointCharClientCfg));
            config.aseCharClientCfg = (ClientConfig*) &data[leUnicastManager_GetClientCfgOffset()];

            leUnicastManager_PublishConnectionStatus(cid, LE_AUDIO_ASCS_UUID_SERVICE, 0);
            (void) LeUnicastManager_InstanceGetByCidOrCreate(cid);
        }
        else if (BtDevice_IsProfileSupportedForDevice(device, DEVICE_PROFILE_LE_AUDIO))
        {
            (void) LeUnicastManager_InstanceGetByCidOrCreate(cid);
        }

        if (LeBapPacsUtilities_RetrieveClientConfig(cid) != NULL)
        {
            leUnicastManager_PublishConnectionStatus(cid, LE_AUDIO_PACS_UUID_SERVICE, 0);
        }
#else
        (void) config;
#endif
        status = BapServerUnicastAddAscsConfig(bapUnicastServiceHandle, cid, data != NULL ? &config : NULL);

        UNICAST_MANAGER_LOG("leUnicastManager_OnEncryptionChanged add ASCS config handle=0x%p cid=0x%x config=0x%x",
                            bapUnicastServiceHandle, cid, data);

        if (status != BAP_SERVER_STATUS_SUCCESS)
        {
            UNICAST_MANAGER_LOG("leUnicastManager_OnEncryptionChanged add ASCS config failed status=%d", status);
            Panic();
        }
    }
}

void LeUnicastManagerTask_Init(void)
{
    LeUnicastManager_TaskReset();

    LeBapUnicastServer_Init(NUMBER_OF_ASES_REQUIRED, LeUnicastManager_GetTask());
#ifdef ENABLE_TMAP_PROFILE
    TmapProfile_Init();
#endif
#ifdef ENABLE_GMAP_PROFILE
    GmapProfile_Init();
#endif
    MicpServer_Init();
    MicpServer_ClientRegister(LeUnicastManager_GetTask());
    GattConnect_RegisterObserver(&leUnicastManager_connect_callbacks);
    GattConnect_UpdateMinAcceptableMtu(leUnicastManagerConfig_GattMtuMinimum());
    GattServiceDiscovery_ClientRegister(LeUnicastManager_GetTask());


#ifdef ENABLE_LEA_CIS_DELEGATION
    appPeerSigClientRegister(LeUnicastManager_GetTask());
#endif /* ENABLE_LEA_CIS_DELEGATION */
}

void LeUnicastManager_TaskReset(void)
{
    le_um_task_data_t *task_data = LeUnicastManager_GetTaskData();
    le_um_instance_t *inst = NULL;

    memset(task_data, 0, sizeof(*task_data));
    LeUnicastManager_GetTask()->handler = leUnicastManager_MessageHandler;

    ARRAY_FOREACH(inst, task_data->le_unicast_instances)
    {
        LeUnicastManager_InstanceReset(inst);
    }
}

#endif /* defined(INCLUDE_LE_AUDIO_UNICAST) */
